#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <log.h>

#include "sc.h"
#include "globals.h"
#include "scu.h"
#include "sculist.h"
#include "scustat.h"

extern log_default_streams_t scul_stat;

scu_list_t scus_procs, scus_streams, scus_bufs, scus_ops;

void scuStatInit(void)
{
  scus_procs = scuListCreate(sizeof(scus_proc_t));
  scus_streams = scuListCreate(sizeof(scus_stream_t));
  scus_bufs = scuListCreate(sizeof(scus_buf_t));
  scus_ops = scuListCreate(sizeof(scus_op_t));
}

scus_stream_t *scuStatAddStream(int id)
{
  scus_stream_t *s;

  lprintf(STREAM(scul_stat.info), "Adding stream %d", id);

  abortOnNULL(STREAM(scul_stat.err),
	      s = (scus_stream_t*)scuListAddItem(&scus_streams));

  s->id = id;
  s->open = 1;

  s->bufs = scuListCreate(sizeof(scus_buf_t));

  return s;
}

scus_buf_t *scuStatAddBuffer(int id, int size,
			     scus_stream_t *s, scus_proc_t *p)
{
  scus_buf_t *b;

  abortOnNULL(STREAM(scul_stat.err), s);
  abortOnNULL(STREAM(scul_stat.err), p);

  lprintf(STREAM(scul_stat.info), "Adding buffer %d (proc %d) to stream %d",
	  id, p->id, s->id);

  abortOnNULL(STREAM(scul_stat.err),
	      b = (scus_buf_t*)scuListAddItem(&scus_bufs));

  b->id = id;
  b->open = 1;
  b->size = size;
  
  b->stream = s;
  scuListAddPreallocatedItem(&s->bufs, b, 0);
  b->proc = p;
  scuListAddPreallocatedItem(&p->bufs, b, 0);
  
  return b;
}

scus_proc_t *scuStatAddProcessor(int id, char *name)
{
  scus_proc_t *p;

  abortOnNULL(STREAM(scul_stat.err), name);

  lprintf(STREAM(scul_stat.info), "Adding processor %d ('%s')", id, name);  

  abortOnNULL(STREAM(scul_stat.err),
	      p = (scus_proc_t*)scuListAddItem(&scus_procs));

  p->id = id;
  abortOnNULL(STREAM(scul_stat.err), p->name = (char*)malloc(strlen(name)+1));
  strcpy(p->name, name);

  p->ops = scuListCreate(sizeof(scus_op_t));
  p->bufs = scuListCreate(sizeof(scus_buf_t));

  return p;
}

scus_op_t *scuStatAddOperation(int id, int type, int line, char *text,
			       scos_thread_t *thread, int threads, scus_proc_t *p)
{
  scus_op_t *o;

  abortOnNULL(STREAM(scul_stat.err), p);

  lprintf(STREAM(scul_stat.info), "Adding operation %d (%d, %d) to processor %d",
	  id, type, line, p->id);

  abortOnNULL(STREAM(scul_stat.err),
	      o = (scus_op_t*)scuListAddItem(&scus_ops));
  o->id = id;
  o->open = 1;
  o->type = type;
  o->line = line;
  abortOnNULL(STREAM(scul_stat.err), o->text = (char*) malloc(strlen(text)+1));
  strcpy(o->text, text);
  abortOnNULL(STREAM(scul_stat.err),
	      o->thread = (scos_thread_t*)malloc(threads*sizeof(scos_thread_t)));
  memcpy(o->thread, thread, threads);
  o->threads = threads;
  o->timer = tmrInit();

  o->proc = p;
  abortOnNULL(STREAM(scul_stat.err),
	      scuListAddPreallocatedItem(&p->ops, o, 0));
  o->args = scuListCreate(sizeof(scus_arg_t));

  return o;
}

scus_arg_t *scuStatOperationAddArgument(scus_op_t *o, int id, int type,
					int intent)
{
  scus_arg_t *a;

  abortOnNULL(STREAM(scul_stat.err), o);

  lprintf(STREAM(scul_stat.info), "Adding operation %d argument %d (%d, %d)",
	  o->id, id, type, intent);

  abortOnNULL(STREAM(scul_stat.err),
	      a = scuListAddItem(&o->args));
  
  a->id = id;
  a->type = type;
  a->intent = intent;
  
  a->op = o;

  return a;
}

int scuStatStartOperation(scus_op_t *o)
{
  lprintf(STREAM(scul_stat.debug), "Starting operation %d", o->id);

  tmrStart(&o->timer);

  return 0;
}

int scuStatDefineArgument(scus_arg_t *a, ...)
{
  va_list ap;

  abortOnNULL(STREAM(scul_stat.err), a);

  lprintf(STREAM(scul_stat.debug), "Defining operation %d argument %d",
	  a->op->id, a->id);

  va_start(ap, a);

  switch (a->type)
  {
    case SC_VAR_STREAM:
      a->i = va_arg(ap, scus_buf_t*);
      break;
    case SC_VAR_INTEGER:
      a->d = *va_arg(ap, int*);
      break;
    case SC_VAR_DOUBLE:
      a->f = *va_arg(ap, double*);
      break;
    default:
      abortErrorl((STREAM(scul_stat.err), "Unknown argument type %d", a->type));
      return -1;
  }

  return 0;
}

int scuStatCloseStream(scus_stream_t *s)
{
  abortOnNULL(STREAM(scul_stat.err), s);

  lprintf(STREAM(scul_stat.debug), "Closing stream %d", s->id);

  s->open = 0;

  return 0;
}

int scuStatCloseBuffer(scus_buf_t *b, int counter, unsigned long bytes)
{
  abortOnNULL(STREAM(scul_stat.err), b);

  lprintf(STREAM(scul_stat.debug), "Closing buffer %d", b->id);

  b->open = 0;
  b->counter = counter;
  b->stream->bytes = bytes;

  return 0;
}

int scuStatCloseOperation(scus_op_t *o)
{
  abortOnNULL(STREAM(scul_stat.err), o);

  lprintf(STREAM(scul_stat.debug), "Closing operation %d", o->id);

  o->open = 0;
  tmrStop(&o->timer);

  return 0;
}

scus_stream_t *scuStatFindStream(int id)
{
  scu_listitem_t *item=NULL;
  scus_stream_t *s;

  while ((s = (scus_stream_t*)scuListEnumerateItems(&scus_streams, &item)))
    if (s->id == id && s->open)
      return s;

  lprintf(STREAM(scul_stat.warning), "Couldn't find stream %d", id);

  return NULL;
}

scus_buf_t *scuStatFindBuffer(int id)
{
  scu_listitem_t *item=NULL;
  scus_buf_t *b;

  while ((b = (scus_buf_t*)scuListEnumerateItems(&scus_bufs, &item)))
    if (b->id == id && b->open)
      return b;

  lprintf(STREAM(scul_stat.warning), "Couldn't find buffer %d", id);

  return NULL;
}

scus_proc_t *scuStatFindProcessor(int id)
{
  scu_listitem_t *item=NULL;
  scus_proc_t *p;

  while ((p = (scus_proc_t*)scuListEnumerateItems(&scus_procs, &item)))
    if (p->id == id)
      return p;

  lprintf(STREAM(scul_stat.warning), "Couldn't find processor %d", id);

  return NULL;
}

scus_op_t *scuStatFindOperation(int id)
{
  scu_listitem_t *item=NULL;
  scus_op_t *o;

  while ((o = (scus_op_t*)scuListEnumerateItems(&scus_ops, &item)))
    if (o->id == id && o->open)
      return o;

  lprintf(STREAM(scul_stat.warning), "Couldn't find operation %d", id);

  return NULL;
}

scus_arg_t *scuStatOperationFindArgument(scus_op_t *o, int id)
{
  scu_listitem_t *item=NULL;
  scus_arg_t *a;

  abortOnNULL(STREAM(scul_stat.err), o);

  while ((a = (scus_arg_t*)scuListEnumerateItems(&o->args, &item)))
    if (a->id == id)
      return a;

  lprintf(STREAM(scul_stat.warning),
	    "Couldn't find operation %d arg %d", o->id, id);

  return NULL;
}

void scuStatWriteStructure(char *name, int pure)
{
  FILE *f;
  scu_listitem_t *item, *item2, *item3;
  scus_proc_t *p;
  scus_stream_t *s;
  scus_buf_t *b;
  scus_op_t *o;
  scus_arg_t *a;
  double maxbytes=0;
  
  /* Find maximum stream size, for weighing line thickness */
  item = NULL;
  while ((s = scuListEnumerateItems(&scus_streams, &item)))
    maxbytes = MAX(maxbytes, s->bytes);

  /* Operation dependency graph */
  abortOnNULL(STREAM(scul_stat.err), f = fopen(name, "w"));
  fprintf(f, "# SMARTCAM TRACE FILE. AUTOMATICALLY GENERATED.\n");
  fprintf(f, "digraph G {\n");

  if (!pure)
  {
    item = NULL;
    while ((s = scuListEnumerateItems(&scus_streams, &item)))
    {
      fprintf(f, "  s%d [shape=box, style=filled, label=\"Stream %d\\n%ld bytes\", comment=\"%d\"]\n", s->uid, s->id, s->bytes, s->id);
      item2 = NULL;
      while ((b = scuListEnumerateItems(&s->bufs, &item2)))
        fprintf(f, "    s%d -> b%d [dir=none, style=\"setlinewidth(%d)\"]\n",
  	      s->uid, b->uid, (int)(s->bytes/maxbytes*SCU_STAT_MAXLINEWIDTH));
    }
  }

  item = NULL;
  while ((p = scuListEnumerateItems(&scus_procs, &item)))
  {
    fprintf(f, "  subgraph cluster_%d {\n", p->uid);
    fprintf(f, "    label=\"%s\";\n", p->name);

    item2 = NULL;
    while ((b = scuListEnumerateItems(&p->bufs, &item2)))
    {
      if (pure)
        fprintf(f, "    b%d [shape=box, label=\"buffer %d\"]\n", b->uid, b->id);
      else
        fprintf(f, "    b%d [shape=box, label=\"stream%d.scs (%d bytes)\", comment=\"%d\"]\n", b->uid, b->counter, b->size, b->id);
    }

    while ((o = scuListEnumerateItems(&p->ops, &item2)))
    {
      if (pure) 
        fprintf(f, "    o%d [label=\"%s\", comment=\"%d\"]\n", o->uid, o->text, o->id);
      else
        fprintf(f, "    o%d [label=\"%s op on line %d\\n%f seconds\", comment=\"%d\"]\n", o->uid, o->text,
	        o->line, tmrGetValueInUs(&o->timer)/1000000.0, o->id);
 
      item3 = NULL;
      while ((a = scuListEnumerateItems(&o->args, &item3)))
	switch (a->type)
	{
	  case SC_VAR_STREAM:
	    if (a->intent == SC_INTENT_IN)
	      fprintf(f, "      b%d -> o%d [style=\"setlinewidth(%d)\"]\n", a->i->uid, o->uid, (int)(a->i->stream->bytes/maxbytes*SCU_STAT_MAXLINEWIDTH));
	    else
	      fprintf(f, "      o%d -> b%d [style=\"setlinewidth(%d)\"]\n", o->uid, a->i->uid, (int)(a->i->stream->bytes/maxbytes*SCU_STAT_MAXLINEWIDTH));
	    break;
	  case SC_VAR_INTEGER:
	    fprintf(f, "      a%d_%d [shape=plaintext, label=\"%d\", comment=\"%d[%d]\"]\n", o->uid, a->uid, a->d, o->id, a->id);
	    if (a->intent == SC_INTENT_IN)
	      fprintf(f, "        a%d_%d -> o%d\n", o->uid, a->uid, o->uid);
	    else
	      fprintf(f, "        o%d -> a%d_%d\n", o->uid, o->uid, a->uid);
	    break;
	  case SC_VAR_DOUBLE:
	    fprintf(f, "      a%d_%d [shape=plaintext, label=\"%f\", comment=\"%d[%d]\"]\n", o->uid, a->uid, a->f, o->id, a->id);
	    if (a->intent == SC_INTENT_IN)
	      fprintf(f, "        a%d_%d -> o%d\n", o->uid, a->uid, o->uid);
	    else
	      fprintf(f, "        o%d -> a%d_%d\n", o->uid, o->uid, a->uid);
	    break;
	}
    }
    fprintf(f, "  }\n");
  }    

  fprintf(f, "}\n");
  fclose(f);
}

void scuStatWriteSchedule(char *name)
{
  FILE *f;
  scu_listitem_t *item; //*item2, *item3
//  scus_proc_t *p;
//  scus_stream_t *s;
//  scus_buf_t *b;
  scus_op_t *o;
//  scus_arg_t *a;

  /* Write operation/time graph */
  abortOnNULL(STREAM(scul_stat.err), f = fopen(name, "w"));
  
  fprintf(f, "%% SMARTCAM TRACE FILE. AUTOMATICALLY GENERATED.\n");
  fprintf(f, "Operations = { ");

  item = NULL;
  while ((o = scuListEnumerateItems(&scus_ops, &item)))
    fprintf(f, "'%s(%d)', ", o->text, o->line);
  
  fprintf(f, "};\n");
  fprintf(f, "Times = [ ");

  item = NULL;
  while ((o = scuListEnumerateItems(&scus_ops, &item)))
    fprintf(f, "[ %f; %f ] ",
	    (double)o->timer.started.tv_sec+o->timer.started.tv_usec/1000000.0,
	    (double)o->timer.stopped.tv_sec+o->timer.stopped.tv_usec/1000000.0);

  fprintf(f, "];\n");
  fprintf(f, "plot(Times, [1;1]*[1:length(Operations)], 'linewidth', 10);\n");
  fprintf(f, "set(gca, 'YTick', [1:length(Operations)]);\n");
  fprintf(f, "set(gca, 'YTickLabel', Operations);\n");

  fclose(f);
}

void scuStatWrite(char *name)
{
  FILE *f;
  scu_listitem_t *item, *item2;
  scus_proc_t *p;
  scus_stream_t *s;
  scus_buf_t *b;
  scus_op_t *o;
  scus_arg_t *a;

  abortOnNULL(STREAM(scul_stat.err), f = fopen(name, "w"));

  fprintf(f, "<trace>\n");

  item = NULL;
  fprintf(f, "  <processors>\n");
  while ((p = scuListEnumerateItems(&scus_procs, &item)))
  {
    fprintf(f, "    <%d>\n", p->uid);
    fprintf(f, "      <E id = \"%d\"/>\n", p->id);
    fprintf(f, "      <E name = \"%s\"/>\n", p->name);

    item2 = NULL;
    fprintf(f, "      <buffers>\n");
    while ((b = scuListEnumerateItems(&p->bufs, &item2)))
      fprintf(f, "        <E %d = \"\"/>\n", b->uid);
    fprintf(f, "      </buffers>\n");

    item2 = NULL;
    fprintf(f, "      <operations>\n");
    while ((o = scuListEnumerateItems(&p->ops, &item2)))
      fprintf(f, "        <E %d = \"\"/>\n", o->uid);
    fprintf(f, "      </operations>\n");

    fprintf(f, "    </%d>\n", p->uid);
  }
  fprintf(f, "  </processors>\n");

  item = NULL;
  fprintf(f, "  <streams>\n");
  while ((s = scuListEnumerateItems(&scus_streams, &item)))
  {
    fprintf(f, "    <%d>\n", s->uid);
    fprintf(f, "      <E id = \"%d\"/>\n", s->id);
    fprintf(f, "      <E bytes = \"%ld\"/>\n", s->bytes);

    item2 = NULL;
    fprintf(f, "      <buffers>\n");
    while ((b = scuListEnumerateItems(&s->bufs, &item2)))
      fprintf(f, "        <E %d = \"\"/>\n", b->uid);
    fprintf(f, "      </buffers>\n");

    fprintf(f, "    </%d>\n", s->uid);
  }
  fprintf(f, "  </buffers>\n");

  item = NULL;
  fprintf(f, "  <buffers>\n");
  while ((b = scuListEnumerateItems(&scus_bufs, &item)))
  {
    fprintf(f, "    <%d>\n", b->uid);
    fprintf(f, "      <E id = \"%d\"/>\n", b->id);
    fprintf(f, "      <E file = \"%d\"/>\n", b->counter);
    fprintf(f, "      <E size = \"%d\"/>\n", b->size);
    fprintf(f, "      <E processor = \"%d\"/>\n", b->proc->uid);
    fprintf(f, "      <E stream = \"%d\"/>\n", b->stream->uid);
    fprintf(f, "    </%d>\n", b->uid);
  }
  fprintf(f, "  </buffers>\n");

  item = NULL;
  fprintf(f, "  <operations>\n");
  while ((o = scuListEnumerateItems(&scus_ops, &item)))
  {
    fprintf(f, "    <%d>\n", o->uid);
    fprintf(f, "      <E id = \"%d\"/>\n", o->id);
    fprintf(f, "      <E type = \"%d\"/>\n", o->type);
    fprintf(f, "      <E line = \"%d\"/>\n", o->line);
    fprintf(f, "      <E text = \"%s\"/>\n", o->text);
    fprintf(f, "      <E processor = \"%d\"/>\n", o->proc->uid);
    fprintf(f, "      <E time = \"%lld\"/>\n", (long long int) tmrGetValueInUs(&o->timer));
    fprintf(f, "      <threads>\n");
    fprintf(f, "      </threads>\n");

    item2 = NULL;
    fprintf(f, "      <arguments>\n");
    while ((a = scuListEnumerateItems(&o->args, &item2)))
    {
      fprintf(f, "        <%d>\n", a->uid);
      fprintf(f, "          <E id = \"%d\"/>\n", a->id);
      fprintf(f, "          <E intent = \"%d\"/>\n", a->intent);
      fprintf(f, "          <E type = \"%d\"/>\n", a->type);
      switch (a->type)
      {
	case SC_VAR_STREAM:
	  fprintf(f, "          <E i = \"%d\"/>\n", a->i->uid);
	  break;
	case SC_VAR_INTEGER:
	  fprintf(f, "          <E d = \"%d\"/>\n", a->d);
	  break;
	case SC_VAR_DOUBLE:
	  fprintf(f, "          <E f = \"%f\"/>\n", a->f);
	  break;
      }
      fprintf(f, "        </%d>\n", a->uid);
    }
    fprintf(f, "      </arguments>\n");

    fprintf(f, "    </%d>\n", o->uid);
  }
  fprintf(f, "  </operations>\n");

  fprintf(f, "</trace>\n");

  fclose(f);
}
